/**
 * @Copyright:Copyright (c) 2007 - 2023
 */
package com.tengtong.duang.app.query.core;

import java.util.LinkedList;
import java.util.Map;

//import com.tengtong.commons.base.KeyValue;
import com.tengtong.commons.collections.CollectionUtils;
import com.tengtong.commons.collections.ListUtils;
import com.tengtong.commons.collections.MapUtils;
import com.tengtong.commons.json.Json;
import com.tengtong.commons.json.Jsons;
import com.tengtong.commons.lang.ExceptionUtils;
import com.tengtong.duang.app.query.core.QueryConstants.BooleanOp;

/**
 * 条件表达式分析器
 *
 * @author lin_jinming
 * @since: v1.0
 */
public class ConditionExpressParser {
    
    /**
     * 分析纯 <@操作符, 表达式Map>
     *
     * @param key
     * @param o
     * @return
     */
    public LinkedList<Object> parseConditionExpress(String key, Object o) {
        var result = ListUtils.newLinkedList();
        parseConditionExpress(result, null, key, o);
        return result;
    }

    /**
     * 分析纯表达式Map （首层默认使用 and 连接）
     *
     * @param key
     * @param o
     * @return
     */
    public LinkedList<Object> parseConditionExpress(Object o) {
        var result = ListUtils.newLinkedList();
        parseConditionExpress(result, null, "@and", o);
        return result;
    }
    
    /**
     * 独立的表达式
     *
     * @param conditions
     * @return
     */
    public LinkedList<Object> parse(Map<String, Object> conditions) {
        LinkedList<Object> datas = new LinkedList<>();
        if(conditions != null && !conditions.isEmpty()) {
            conditions.forEach((name, o)->{
                var nameStr = name;
                if(name.charAt(0)  == '@') {
                    nameStr = name.substring(1);
                    if(BooleanOp.check(nameStr)) {
                        parseConditionExpress(datas, null, nameStr, o);
                    } else {
                        throw ExpressParseException.of("语法错误|表达式不支持语句：{}", name);
                    }
                } else {
                    putFieldValueExpress(datas, null, name, o);
                }                
            });
        }        
        return datas;
    }
    
    /**
     * 对表达式对象进行处理
     *
     * @param op        上级操作符，如果没有就为 null
     * @param newOpStr  新的操作符
     * @param o         数值，列表，map
     */
    public void parseConditionExpress(LinkedList<Object> datas, BooleanOp op, String newOpStr, Object o) {
        var newOp = BooleanOp.fromValue(newOpStr);
        if(newOp != null){
            if(newOp == BooleanOp.NOT){
                if(o instanceof String fields){ // 字符串类型时，使用逗号分隔成多个字段
                    datas.push(newOp);
                    datas.push(fields.split(","));
                } else if(o instanceof Json || o instanceof Map) {
                    var tmps = ListUtils.newLinkedList();
                    parseExpressMap(tmps, MapUtils.asLinkedMap(o), newOp);
                    
                    if(CollectionUtils.notEmpty(tmps)) {
                        datas.push(newOp);
                        datas.push(tmps);
                    }
                }
            } else {
                // name 为一个布尔量，而 o 一定是一个对象
                if(o instanceof Json || o instanceof Map) {
                    var tmps = ListUtils.newLinkedList();
                    parseExpressMap(tmps, MapUtils.asLinkedMap(o), newOp);
                    
                    if(CollectionUtils.notEmpty(tmps)) {
                        if(datas.peek() != null) {
                            datas.push(op == null ? BooleanOp.AND:op);
                        }
                        datas.push(tmps);
                    }
                } else {
                    if(o instanceof Jsons js) { // 条件数组
                        for(var json: js) {
                            var tmps = ListUtils.newLinkedList();
                            parseExpressMap(tmps, json.toMap(), newOp);
                            
                            if(CollectionUtils.notEmpty(tmps)) {
                                if(datas.peek() != null) {
                                    datas.push(newOp);
                                }
                                datas.push(tmps);
                            }
                        }
                    } else {                        
                        throw ExceptionUtils.ofRuntime("语法错误|布尔表达式的值应该是对象");
                    }
                }

            }
            
        } else {
            throw ExceptionUtils.ofRuntime("语法错误|不支持的布尔操作:%s", newOp);  
        }        
    }
    
    /**
     * 对单纯的表达式对象进行分析
     *
     * @param conditions
     * @param op
     * @return
     */
    private void parseExpressMap(LinkedList<Object> datas, Map<String, Object> conditions, BooleanOp op) {
        if(conditions != null && !conditions.isEmpty()) {
            conditions.forEach((name, o)->{
                var nameStr = name;
                if(name.charAt(0)  == '@') {
                    nameStr = name.substring(1);
                    var tmps = ListUtils.newLinkedList();
                    parseConditionExpress(tmps, op, nameStr, o);
                    
                    if(ListUtils.notEmpty(tmps)) {
                        
                        datas.push(tmps);
                    }
                } else {
                    putFieldValueExpress(datas, op, name, o);
                }                
            });
        }
    }
    
    /**
     * 将单个的关系表达式放入队列
     *
     * @param datas
     * @param op
     * @param name
     * @param o
     */
    void putFieldValueExpress(LinkedList<Object> datas, BooleanOp op, String name, Object o) {
        FieldValueExpress keyValue = FieldValueExpress.of(name, o);
        if(!datas.isEmpty()) {
            if(!BooleanOp.check(datas.peek())) {
                // 栈顶是数据，就要压一个操作入栈
                if(op != null) {
                    datas.push(op); // 压操作符入栈
                } else {
                    datas.push(BooleanOp.AND);
                    // 语法错误，即都有数据，但没有操作
                    // throw ExceptionUtils.ofRuntime("语法错误|都有数据没操作");
                }
            }
        }
        datas.push(keyValue);
    }

}
